#ifndef UTILITY_H
#define UTILITY_H

#include "FrameBufferHandler.h"
#include "ObjHandler.h"
#include "ShaderHandler.h"
#include "TextureHandler.h"
#include "floatimage.H"
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <glut.h>
#include <glui.h>

#define LIGHTSPACE	0
#define CAMERASPACE	1
#define BLURSTEP	4

static GLint    ImageWidth=500;
static GLint    ImageHeight=500;

OBJHandler mesh;
GLuint *displayListID;
GLuint *texture;
GLuint diffuseAlbedo;

CGcontext context;
CGprofile vProfile, fProfile;

ShaderHandler vRender, fRender, vIrradiance, fIrradiance, vGaussianU, fGaussianU, vGaussianV, fGaussianV;

FrameBufferHandler ShadowBuffer[2], IrradianceBuffer, GaussianBuffer[BLURSTEP], InterBuffer;

GLfloat MVMatrix[16];
GLfloat ProjectMatrix[16];

GLfloat ProjectionLight[16];
GLfloat ProjectionLightOgl[16];

GLfloat ObjectToLight[16];
GLfloat ObjectToLightOgl[16];

GLfloat ProjectiveLight[16];
GLfloat ProjectiveLightOgl[16];

float obj_rot[16];
float lig_rot[16];

GLfloat LightProjectMatrix[16];
GLfloat LightMVMatrix[16];

GLfloat light_position[4]={0.0, 0.0, 3.0, 1.0};

GLfloat eye_position[4]={0,0,3,1};

int diffuseWidth, diffuseHeight;

GLfloat light_distance=5;

int gaussianIrradiance;
float gaussianWidth;
int gaussianStretch;

GLUI *glui;

void InitModel();
void InitGL();
void drawModel();
void LoadShader();
void InitShader();
void InitCG();
void LoadTexture();
void UpdateMatrix();
void TransposeMatrix(float *out, const float *m);
void InitFrameBuffer();

void cgErrorCallback(void)
{
	CGerror lastError = cgGetError();
	if(lastError) {
		const char *listing = cgGetLastListing(context);
		printf("%s\n", cgGetErrorString(lastError));
		printf("%s\n", listing);
		exit(-1);
	}
}

void InitGL()
{
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glDepthFunc(GL_LESS);
	glEnable(GL_DEPTH_TEST);
	//glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
	//glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
	//glEnable(GL_TEXTURE_GEN_S); 
	//glEnable(GL_TEXTURE_GEN_T); 

	glViewport(0, 0, (GLsizei) ImageWidth, (GLsizei) ImageHeight);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0f,(GLfloat) ImageWidth / (GLfloat)ImageHeight , 0.5f, -1000.0f);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void InitModel()
{
	displayListID = (GLuint*)malloc(sizeof(GLuint));


	mesh.LoadOBJascii("dragon.obj");

	displayListID[0] = glGenLists(1);
	glNewList(displayListID[0], GL_COMPILE);
	glBegin(GL_TRIANGLES);
	for(int j=0; j<mesh.numTris; j++){
		int indexV1 = mesh.tris[j].v1;
		int indexV2 = mesh.tris[j].v2;
		int indexV3 = mesh.tris[j].v3;
		int indexUV1 = mesh.tris[j].uv1;
		int indexUV2 = mesh.tris[j].uv2;
		int indexUV3 = mesh.tris[j].uv3;
		glTexCoord2f(mesh.uvs[indexUV1].u, 1-mesh.uvs[indexUV1].v); 
		glNormal3f(mesh.normal[indexV1].x, mesh.normal[indexV1].y, mesh.normal[indexV1].z);
		//glColor3f(mesh.uvs[indexUV1].u, 1-mesh.uvs[indexUV1].v, 0);
		glVertex3f(mesh.verts[indexV1].x, mesh.verts[indexV1].y, mesh.verts[indexV1].z);

		glTexCoord2f(mesh.uvs[indexUV2].u, 1-mesh.uvs[indexUV2].v); 
		glNormal3f(mesh.normal[indexV2].x, mesh.normal[indexV2].y, mesh.normal[indexV2].z);
		//glColor3f(mesh.uvs[indexUV2].u, 1-mesh.uvs[indexUV2].v, 0);
		glVertex3f(mesh.verts[indexV2].x, mesh.verts[indexV2].y, mesh.verts[indexV2].z);

		glTexCoord2f(mesh.uvs[indexUV3].u, 1-mesh.uvs[indexUV3].v); 
		glNormal3f(mesh.normal[indexV3].x, mesh.normal[indexV3].y, mesh.normal[indexV3].z);
		//glColor3f(mesh.uvs[indexUV3].u, 1-mesh.uvs[indexUV3].v, 0);
		glVertex3f(mesh.verts[indexV3].x, mesh.verts[indexV3].y, mesh.verts[indexV3].z);
	}
	glEnd();
	glEndList();
}

void drawModel()
{
	glCallList(displayListID[0]);
}

void drawQuad()
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glPolygonMode(GL_FRONT,GL_FILL);

	glBegin(GL_QUADS);
	glTexCoord2f(0.0, 0.0); 
	glVertex2f(0.0, 0.0);
	glTexCoord2f(1.0, 0.0); 
	glVertex2f(1.0, 0.0);
	glTexCoord2f(1.0, 1.0); 
	glVertex2f(1.0, 1.0);
	glTexCoord2f(0.0, 1.0); 
	glVertex2f(0.0, 1.0);
	glEnd();
}

void LoadShader()
{
	vRender.m_context=context;
	vRender.Load("vRender.cg",CG_GL_VERTEX);

	fRender.m_context=context;
	fRender.Load("fRender.cg",CG_GL_FRAGMENT);

	vIrradiance.m_context=context;
	vIrradiance.Load("vIrradiance.cg",CG_GL_VERTEX);

	fIrradiance.m_context=context;
	fIrradiance.Load("fIrradiance.cg",CG_GL_FRAGMENT);

	vGaussianU.m_context=context;
	vGaussianU.Load("vGaussianU.cg",CG_GL_VERTEX);

	fGaussianU.m_context=context;
	fGaussianU.Load("fGaussianU.cg",CG_GL_FRAGMENT);

	vGaussianV.m_context=context;
	vGaussianV.Load("vGaussianV.cg",CG_GL_VERTEX);

	fGaussianV.m_context=context;
	fGaussianV.Load("fGaussianV.cg",CG_GL_FRAGMENT);

}

void InitShader()
{

	vRender.m_parameters.clear();

	fRender.m_parameters.clear();
	fRender.AddParameter("blur0",CG_GL_TEXTURE_2D,&(GaussianBuffer[0].framebuffer.texID[0]));
	fRender.AddParameter("blur1",CG_GL_TEXTURE_2D,&(GaussianBuffer[1].framebuffer.texID[0]));
	fRender.AddParameter("blur2",CG_GL_TEXTURE_2D,&(GaussianBuffer[2].framebuffer.texID[0]));
	fRender.AddParameter("blur3",CG_GL_TEXTURE_2D,&(GaussianBuffer[3].framebuffer.texID[0]));
	fRender.AddParameter("specular",CG_GL_TEXTURE_2D,&(IrradianceBuffer.framebuffer.texID[2]));

	vIrradiance.m_parameters.clear();
	vIrradiance.AddParameter("Proj",CG_GL_ARBITRARY_MTX,ProjectMatrix);
	vIrradiance.AddParameter("MV",CG_GL_ARBITRARY_MTX,MVMatrix);


	fIrradiance.m_parameters.clear();
	fIrradiance.AddParameter("LProj",CG_GL_ARBITRARY_MTX,ProjectiveLightOgl);
	fIrradiance.AddParameter("shadowMap",CG_GL_TEXTURE_2D,&(ShadowBuffer[LIGHTSPACE].framebuffer.texID[0]));
	fIrradiance.AddParameter("diffuseAlbedo",CG_GL_TEXTURE_2D,&diffuseAlbedo);
	fIrradiance.AddParameter("light",CG_GL_VECTOR_4D,light_position);
	fIrradiance.AddParameter("eye",CG_GL_VECTOR_4D,eye_position);

	vGaussianU.m_parameters.clear();

	fGaussianU.m_parameters.clear();
	fGaussianU.AddParameter("irradiance",CG_GL_TEXTURE_2D,&gaussianIrradiance);
	fGaussianU.AddParameter("position",CG_GL_TEXTURE_2D,&gaussianStretch);
	fGaussianU.AddParameter("matte",CG_GL_TEXTURE_2D,&IrradianceBuffer.framebuffer.texID[1]);
	fGaussianU.AddParameter("width",CG_GL_VECTOR_1D,&gaussianWidth);
	
	vGaussianV.m_parameters.clear();

	fGaussianV.m_parameters.clear();
	fGaussianV.AddParameter("irradiance",CG_GL_TEXTURE_2D,&gaussianIrradiance);
	fGaussianV.AddParameter("position",CG_GL_TEXTURE_2D,&gaussianStretch);
	fGaussianV.AddParameter("matte",CG_GL_TEXTURE_2D,&IrradianceBuffer.framebuffer.texID[1]);
	fGaussianV.AddParameter("width",CG_GL_VECTOR_1D,&gaussianWidth);
	
}

void InitCG()
{
	cgSetErrorCallback(cgErrorCallback);
	context=cgCreateContext();
}

void LoadTexture()
{
	texture=(GLuint*)malloc(8*sizeof(GLuint));

	TextureHandler albedoHandler;
	albedoHandler.Load("marble1.pfm");
	texture[0]=albedoHandler.ogl_id;

	diffuseAlbedo=texture[0];

	diffuseWidth=albedoHandler.img_width;
	diffuseHeight=albedoHandler.img_height;
}

void UpdateMatrix()
{
	float length=0;

	for (int i=0;i<3;i++)
	{
		light_position[i]=0;
		for (int j=0;j<3;j++)
		{
			light_position[i]+=lig_rot[j*4+i];
		}
		length+=light_position[i]*light_position[i];
	}	

	length=sqrt(length);

	for (int i=0;i<3;i++)
	{
		light_position[i]/=length;
		light_position[i]*=light_distance;
	}

	glClearColor(0,0,0,0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glViewport(0, 0, (GLsizei) diffuseWidth, (GLsizei) diffuseHeight);



	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(eye_position[0],eye_position[1],eye_position[2],0,0,0 ,0,1,0);
	glMultMatrixf(obj_rot);

	glGetFloatv(GL_PROJECTION_MATRIX,ProjectMatrix);
	glGetFloatv(GL_MODELVIEW_MATRIX,MVMatrix);	

	float M_lw[16]={1,0,0,0,
		0,1,0,0,
		0,0,1,light_distance,
		0,0,0,1}; 
	float M_ow[16]={1,0,0,0,
		0,1,0,0,
		0,0,1,0,
		0,0,0,1};

	mat4f ligM(lig_rot);
	mat4f objM(obj_rot);

	float eyeO[4]={0,0,3,1};
	float ligO[4]={0,0,3,1};

	vec4f lig(ligO);
	vec4f eye(eyeO);


	mat4f olM=objM.Inverse()*ligM;

	lig=olM.Inverse()*lig;
	eye=objM*eye;

	for (int i=0;i<4;i++)
	{
		for (int j=0;j<4;j++)
		{
			M_ow[i*4+j]=olM[i][j];
			MVMatrix[i*4+j]=objM[i][j];
		}

		light_position[i]=lig[i];
	}

	MVMatrix[14]=-eye_position[2];


	mat4f Mow(M_ow);
	mat4f Mlw(M_lw);
	mat4f Mol=Mlw.Inverse()*Mow;

	for (int i=0;i<4;i++)
	{
		for (int j=0;j<4;j++)
		{
			ObjectToLight[i*4+j]=Mol[i][j];
		}
	}

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(30,(GLfloat) ImageWidth / (GLfloat)ImageHeight, 2.5f, 1000.0f);
	//gluPerspective(45.0f,(GLfloat) ImageWidth / (GLfloat)ImageHeight , 0.5f, 10.0f);
	glGetFloatv(GL_PROJECTION_MATRIX, ProjectionLightOgl );

	TransposeMatrix(ProjectionLight,ProjectionLightOgl);

	mat4f Mprojectionl(ProjectionLight);

	mat4f Mbias(0.5f, 0.0f, 0.0f, 0.0f,
		0.0f, 0.5f, 0.0f, 0.0f,
		0.0f, 0.0f, 0.5f, 0.0f,
		0.5f, 0.5f, 0.5f, 1.0f);

	Mbias=Mbias.Transpose();

	mat4f Mprojectivel=Mbias*Mprojectionl*Mol;


	for (int i=0;i<4;i++)
	{
		for (int j=0;j<4;j++)
		{
			ProjectiveLight[i*4+j]=Mprojectivel[i][j];
		}
	}


}

void TransposeMatrix( float *out, const float *m )
{
	for (int i=0;i<4;i++)
	{
		for (int j=0;j<4;j++)
		{
			out[i*4+j]=m[j*4+i];
		}
	}
}

void InitFrameBuffer()
{
	glewInit();

	GLuint irradianceID, bufferID;
	GLuint shadowID[2];

	glGenFramebuffersEXT(2,shadowID);
	glGenFramebuffersEXT(1,&irradianceID);
	glGenFramebuffersEXT(1,&bufferID);

	ShadowBuffer[LIGHTSPACE].SetBufferID(shadowID[LIGHTSPACE]);
	ShadowBuffer[LIGHTSPACE].SetUpFBO(DEP,ImageWidth*2,ImageHeight*2);

	ShadowBuffer[CAMERASPACE].SetBufferID(shadowID[CAMERASPACE]);
	ShadowBuffer[CAMERASPACE].SetUpFBO(DEP,ImageWidth*2,ImageHeight*2);

	IrradianceBuffer.SetBufferID(irradianceID);
	IrradianceBuffer.SetUpFBO(TEX_DEP,ImageWidth,ImageHeight,3);

	InterBuffer.SetBufferID(bufferID);
	InterBuffer.SetUpFBO(TEX,ImageWidth,ImageHeight);

	for (int i=0;i<BLURSTEP;i++)
	{
		GaussianBuffer[i].SetBufferID(bufferID);
		GaussianBuffer[i].SetUpFBO(TEX,ImageWidth,ImageHeight);
	}
}

#endif